package config

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"gopkg.in/yaml.v3"
)

type RendererFile struct {
	sort int

	arrFilepath []string
}

func NewRendererFile(sort int) *RendererFile {
	return &RendererFile{
		sort: sort,
	}
}

func (r *RendererFile) Name() string {
	return "file"
}

func (r *RendererFile) Sort() int {
	return r.sort
}

func (r *RendererFile) Render(body reflect.Value, desc *ConfigDesc) error {
	for _, fp := range r.arrFilepath {
		ext := filepath.Ext(fp)
		content, err := os.ReadFile(fp)
		if err != nil {
			return err
		}

		ctx := &fileContext{
			ext: ext,
		}

		nv, assist, err := ctx.load(desc, content)
		if err != nil {
			return err
		}
		if err := ctx.merge(desc, nv, body, assist); err != nil {
			return err
		}
	}

	return nil
}

type fileContext struct {
	ext string
}

func (ctx *fileContext) load(desc *ConfigDesc, bs []byte) (reflect.Value, map[string]interface{}, error) {
	v := reflect.New(desc.Type)
	vi := v.Interface().(IConfigDefault)
	vi.ToDefault(vi)

	assist := map[string]interface{}{}

	switch ctx.ext {
	case ".json":
		if err := json.Unmarshal(bs, vi); err != nil {
			return reflect.Value{}, nil, err
		}
		if err := json.Unmarshal(bs, &assist); err != nil {
			return reflect.Value{}, nil, err
		}
		//修正tag名称和字段名称不一致的情况
		for i := 0; i < v.Elem().NumField(); i++ {
			tag := v.Elem().Type().Field(i).Tag.Get("json")
			if tag == "" {
				continue
			}
			tagName := strings.Split(tag, ",")[0]
			val := assist[tagName]
			if val == nil {
				continue
			}
			newName := strings.ToLower(strings.ReplaceAll(tagName, "_", ""))
			delete(assist, tagName)
			assist[newName] = val
		}
	case ".yaml":
		if err := yaml.Unmarshal(bs, vi); err != nil {
			return reflect.Value{}, nil, err
		}
		if err := yaml.Unmarshal(bs, &assist); err != nil {
			return reflect.Value{}, nil, err
		}
		//修正tag名称和字段名称不一致的情况
		for i := 0; i < v.Elem().NumField(); i++ {
			tag := v.Elem().Type().Field(i).Tag.Get("yaml")
			if tag == "" {
				continue
			}
			tagName := strings.Split(tag, ",")[0]
			val := assist[tagName]
			if val == nil {
				continue
			}
			newName := strings.ToLower(strings.ReplaceAll(tagName, "_", ""))
			delete(assist, tagName)
			assist[newName] = val
		}
	default:
		return reflect.Value{}, nil, fmt.Errorf("config: unknown file extension %s", ctx.ext)
	}

	return v, assist, nil
}

func (ctx *fileContext) marshal(v reflect.Value) ([]byte, error) {
	for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
		v = v.Elem()
	}
	switch ctx.ext {
	case ".json":
		return json.Marshal(v.Interface())
	case ".yaml":
		return yaml.Marshal(v.Interface())
	default:
		return nil, fmt.Errorf("config: unknown file extension %s", ctx.ext)
	}
}

func (ctx *fileContext) merge(desc *ConfigDesc, from, to reflect.Value, assist map[string]interface{}) error {
	for from.Kind() == reflect.Ptr || from.Kind() == reflect.Interface {
		from = from.Elem()
	}
	for to.Kind() == reflect.Ptr || to.Kind() == reflect.Interface {
		to = to.Elem()
	}

	for i := 0; i < to.NumField(); i++ {
		ft := to.Type().Field(i)
		if _, exist := assist[strings.ToLower(ft.Name)]; !exist {
			continue
		}

		if subDesc, ok := desc.Children[strings.ToLower(ft.Name)]; ok {
			bs, err := ctx.marshal(from.Field(i))
			if err != nil {
				return err
			}
			nv, assist, err := ctx.load(subDesc, bs)
			if err != nil {
				return err
			}
			if err := ctx.merge(subDesc, nv, to.Field(i), assist); err != nil {
				return err
			}
		} else if subMap, ok := desc.ChildrenMap[strings.ToLower(ft.Name)]; ok {
			fromField := from.Field(i)
			if fromField.IsNil() {
				continue
			}
			if fromField.Kind() != reflect.Map {
				return fmt.Errorf("config: field %s is not map", ft.Name)
			}
			toField := to.Field(i)
			if toField.IsNil() {
				toField.Set(reflect.MakeMap(toField.Type()))
			}
			for _, key := range fromField.MapKeys() {
				desc := subMap[strings.ToLower(key.String())]
				if desc == nil {
					continue
				}
				fromValue := fromField.MapIndex(key)
				toValue := toField.MapIndex(key)
				if toValue.IsNil() {
					toValue = reflect.New(subDesc.Type)
					toField.SetMapIndex(key, toValue)
				}
				bs, er := ctx.marshal(fromValue)
				if er != nil {
					return er
				}
				nv, assist, err := ctx.load(desc, bs)
				if err != nil {
					return err
				}
				if err := ctx.merge(desc, nv, toValue, assist); err != nil {
					return err
				}
			}

		} else {
			to.Field(i).Set(from.Field(i))
		}
	}

	return nil
}

func (r *RendererFile) ConfigFilepath(fp string) {
	r.ConfigFilepaths([]string{fp})
}

func (r *RendererFile) ConfigFilepaths(arr []string) {
	r.arrFilepath = arr
}

func (r *RendererFile) Filepaths() []string {
	return r.arrFilepath
}
